﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Media.Imaging;
using System.Xml.Linq;

namespace SLGameEngine
{
    public class Editor
    {
        private Canvas MainCanvas = null;
        private bool _floodFilling = false;
        private TerrainMgr _terrainMgr = null;

        public Editor(Canvas mainCanvas, TerrainMgr terrainMgr)
        {
            _terrainMgr = terrainMgr;
            MainCanvas = mainCanvas;
        }

        public ObjectTerrain AddTerrainObject(Point pt, int layer, ObjectTerrain terrainTemplate)
        {
            int tileX = (int)(pt.X / _terrainMgr.TerrainTileRadius);
            int tileY = (int)(pt.Y / _terrainMgr.TerrainTileRadius);

            ObjectTerrain terrainObj = _terrainMgr.TerrainTiles[tileY, tileX];
            Image img = terrainTemplate.GetLayerImage(0);
            int baseObjectID = terrainTemplate.GetLayerBaseObjectID(0);

            terrainObj.SetLayer(layer, baseObjectID, ((BitmapImage)img.Source).UriSource.OriginalString);
            return terrainObj;
        }

        public void ApplyOpacityMask(Point pt, int layer, int opacityID, Image opacityImage)
        {
            int tileX = (int)(pt.X / _terrainMgr.TerrainTileRadius);
            int tileY = (int)(pt.Y / _terrainMgr.TerrainTileRadius);

            ObjectTerrain terrainObj = _terrainMgr.TerrainTiles[tileY, tileX];
            terrainObj.ApplyOpacityMask(layer, opacityID, opacityImage);
        }

        public void DeleteObject(SLGameEngine.ObjectBase obj)
        {
            if (MainCanvas.Children.Contains(obj) && obj.GetType() != typeof(SLGameEngine.ObjectTerrain))
                MainCanvas.Children.Remove(obj);

            if (obj.GetType() == typeof(SLGameEngine.ObjectCreature) && ObjectMgr.CreatureObjects.Contains((SLGameEngine.ObjectCreature)obj))
                ObjectMgr.CreatureObjects.Remove((SLGameEngine.ObjectCreature)obj);
            else if (obj.GetType() == typeof(SLGameEngine.ObjectMap) && ObjectMgr.MapObjects.Contains((SLGameEngine.ObjectMap)obj))
                ObjectMgr.MapObjects.Remove((SLGameEngine.ObjectMap)obj);
            else if (obj.GetType() == typeof(SLGameEngine.ObjectGame) && ObjectMgr.GameObjects.Contains((SLGameEngine.ObjectGame)obj))
                ObjectMgr.GameObjects.Remove((SLGameEngine.ObjectGame)obj);
            else if (obj.GetType() == typeof(SLGameEngine.ObjectTerrain))
                ((SLGameEngine.ObjectTerrain)obj).ClearLayers();
        }

        private ObjectGame AddGameObject(SLGameEngine.ObjectBase obj, double x, double y)
        {
            SLGameEngine.ObjectGame gameObj = (ObjectGame)((ObjectGame)obj).Clone(0);
            gameObj.CurrentX =  x;
            gameObj.CurrentY = y;

            ObjectMgr.GameObjects.Add((ObjectGame)gameObj);
            MainCanvas.Children.Add(gameObj);

            return gameObj;
        }

        private ObjectCreature AddCreatureObject(ObjectBase obj, double x, double y)
        {
            SLGameEngine.ObjectCreature creatureObj = (ObjectCreature)((ObjectCreature)obj).Clone(0);
            creatureObj.CurrentX  = x;
            creatureObj.CurrentY = y;
            creatureObj.Animate(Enums.Direction.South, SLGameEngine.Enums.AnimationType.Walk, false);
            ObjectMgr.CreatureObjects.Add((ObjectCreature)creatureObj);
            MainCanvas.Children.Add(creatureObj);

            return creatureObj;
        }

        private ObjectMap AddMapObject(SLGameEngine.ObjectBase obj, double x, double y)
        {
            ObjectMap mapObject = (ObjectMap)((ObjectMap)obj).Clone(0);
            mapObject.CurrentX = x;
            mapObject.CurrentY = y;

            ObjectMgr.MapObjects.Add((SLGameEngine.ObjectMap)mapObject);
            MainCanvas.Children.Add(mapObject);

            return mapObject;
        }

        public ObjectBase DrawObject(double x, double y, ObjectBase obj)
        {
            if (obj.GetType() == typeof(SLGameEngine.ObjectGame))
                return AddGameObject(obj, x, y);
            else if (obj.GetType() == typeof(SLGameEngine.ObjectCreature))
                return AddCreatureObject(obj, x, y);
            else if (obj.GetType() == typeof(SLGameEngine.ObjectMap))
                return AddMapObject(obj, x, y);

            return null;
        }

        private void FloodFill(int x, int y, int layer, string targetFileName, string srcFileName, int baseObjectID)
        {
            ObjectTerrain currentTerrain = _terrainMgr.TerrainTiles[y, x];
            Image img = currentTerrain.GetLayerImage(layer);
            string fileName = Utils.GetImageFileName(img);

            if (fileName != targetFileName)
                return;
            if (fileName == srcFileName)
                return;

            currentTerrain.SetLayer(layer, baseObjectID, srcFileName);

            if (x - 1 >= 0)
                FloodFill(x - 1, y,  layer, targetFileName, srcFileName, baseObjectID);

            if (x + 1 < _terrainMgr.TerrainWidthInTiles)
                FloodFill(x + 1, y, layer, targetFileName, srcFileName, baseObjectID);

            if (y - 1 >= 0)
                FloodFill(x, y - 1, layer, targetFileName, srcFileName, baseObjectID);

            if (y + 1 < _terrainMgr.TerrainHeightInTiles)
                FloodFill(x, y + 1, layer,  targetFileName, srcFileName, baseObjectID);
        }

        public void Fill(double x, double y, int layer, ObjectTerrain terrainObject)
        {
            if (false == _floodFilling)
            {
                _floodFilling = true;
                int tileX = (int)(x / _terrainMgr.TerrainTileRadius);
                int tileY = (int)(y / _terrainMgr.TerrainTileRadius);

                SLGameEngine.ObjectTerrain targetTerrain = _terrainMgr.TerrainTiles[tileY, tileX];
                Image targetImg = targetTerrain.GetLayerImage(layer);
                string targetFileName = Utils.GetImageFileName(targetImg);
                targetTerrain.BaseObjectID = terrainObject.BaseObjectID;
                Image srcImg = terrainObject.GetLayerImage(0);
                string srcFileName = Utils.GetImageFileName(srcImg);

                FloodFill(tileX, tileY, layer, targetFileName, srcFileName, terrainObject.BaseObjectID);
                _floodFilling = false;
            }
        }

        public string Dropper(Point pt, int layer)
        {
            int tileX = (int)(pt.X / _terrainMgr.TerrainTileRadius);
            int tileY = (int)(pt.Y / _terrainMgr.TerrainTileRadius);

            SLGameEngine.ObjectTerrain terrainObj = _terrainMgr.TerrainTiles[tileY, tileX];

            return Utils.GetImageFileName(terrainObj.GetLayerImage(layer));
        }

        public void SaveMap()
        {
            SaveFileDialog sfd = new SaveFileDialog();
            sfd.Filter = "map files (*.xml)|*.xml|All files (*.*)|*.*";
            sfd.ShowDialog();
            System.IO.Stream stream = sfd.OpenFile();
            if (null != stream)
            {

                XDocument document = new XDocument();

                document.Declaration = new XDeclaration("1.0", "utf-8", "true");

                XElement rootElement = new XElement(SLGameEngine.Consts.MapAttributeMap);
                document.Add(rootElement);

                XElement childElement = new XElement(SLGameEngine.Consts.MapAttributeMapName);
                childElement.Value = _terrainMgr.MapName;
                rootElement.Add(childElement);

                childElement = new XElement(SLGameEngine.Consts.MapAttributeWidthInTiles);
                childElement.Value = Convert.ToString(_terrainMgr.TerrainWidthInTiles);
                rootElement.Add(childElement);

                childElement = new XElement(SLGameEngine.Consts.MapAttributeHeightInTiles);
                childElement.Value = Convert.ToString(_terrainMgr.TerrainHeightInTiles);
                rootElement.Add(childElement);

                childElement = new XElement(SLGameEngine.Consts.MapAttributeTileRadius);
                childElement.Value = Convert.ToString(_terrainMgr.TerrainTileRadius);
                rootElement.Add(childElement);

                childElement = new XElement(SLGameEngine.Consts.MapAttributeLayerCount);
                childElement.Value = Convert.ToString(SLGameEngine.Consts.TerrainLayerCount);
                rootElement.Add(childElement);

                foreach (SLGameEngine.ObjectMap mapObject in ObjectMgr.MapObjects)
                {
                    childElement = new XElement(SLGameEngine.Consts.MapAttributeMapObject);
                    childElement.SetAttributeValue(SLGameEngine.Consts.MapAttributeBaseObjectID, mapObject.BaseObjectID);
                    childElement.SetAttributeValue(SLGameEngine.Consts.MapAttributePosX, Convert.ToString(mapObject.GetValue(Canvas.LeftProperty)));
                    childElement.SetAttributeValue(SLGameEngine.Consts.MapAttributePosY, Convert.ToString(mapObject.GetValue(Canvas.TopProperty)));
                    rootElement.Add(childElement);
                }

                foreach (SLGameEngine.ObjectGame gameObject in ObjectMgr.GameObjects)
                {
                    childElement = new XElement(SLGameEngine.Consts.MapAttributeGameObject);
                    childElement.SetAttributeValue(SLGameEngine.Consts.MapAttributeBaseObjectID, gameObject.BaseObjectID);
                    childElement.SetAttributeValue(SLGameEngine.Consts.MapAttributePosX, Convert.ToString(gameObject.GetValue(Canvas.LeftProperty)));
                    childElement.SetAttributeValue(SLGameEngine.Consts.MapAttributePosY, Convert.ToString(gameObject.GetValue(Canvas.TopProperty)));
                    rootElement.Add(childElement);
                }
                foreach (SLGameEngine.ObjectCreature creatureObject in ObjectMgr.CreatureObjects)
                {
                    childElement = new XElement(SLGameEngine.Consts.MapAttributeCreatureObject);
                    childElement.SetAttributeValue(SLGameEngine.Consts.MapAttributeBaseObjectID, creatureObject.BaseObjectID);
                    childElement.SetAttributeValue(SLGameEngine.Consts.MapAttributePosX, Convert.ToString(creatureObject.GetValue(Canvas.LeftProperty)));
                    childElement.SetAttributeValue(SLGameEngine.Consts.MapAttributePosY, Convert.ToString(creatureObject.GetValue(Canvas.TopProperty)));
                    rootElement.Add(childElement);
                }

                for (int i = 0; i < SLGameEngine.Consts.TerrainLayerCount; i++)
                {
                    childElement = new XElement(SLGameEngine.Consts.MapAttributeTileIDsLayer + i.ToString());

                    string tileIDs = String.Empty;
                    for (int y = 0; y < _terrainMgr.TerrainHeightInTiles; y++)
                    {
                        for (int x = 0; x < _terrainMgr.TerrainWidthInTiles; x++)
                        {
                            int ID = _terrainMgr.TerrainTiles[y, x].GetLayerBaseObjectID(i);
                            tileIDs += Convert.ToString(ID) + ",";
                        }
                    }
                    childElement.Value = tileIDs;
                    rootElement.Add(childElement);
                }

                for (int i = 0; i < SLGameEngine.Consts.TerrainLayerCount; i++)
                {
                    childElement = new XElement(SLGameEngine.Consts.MapAttributeOpacityIDsLayer + i.ToString());

                    string opacityIDs = String.Empty;
                    for (int y = 0; y < _terrainMgr.TerrainHeightInTiles; y++)
                    {
                        for (int x = 0; x < _terrainMgr.TerrainWidthInTiles; x++)
                        {
                            int ID = _terrainMgr.TerrainTiles[y, x].GetLayerOpacityID(i);
                            opacityIDs += Convert.ToString(ID) + ",";
                        }
                    }
                    childElement.Value = opacityIDs;
                    rootElement.Add(childElement);
                }

                document.Save(stream);

                stream.Flush();
                stream.Close();
            }
        }

        public void OpenMap(System.IO.Stream stream)
        {
            string mapName = String.Empty;
            int widthInTiles = 0;
            int heightInTiles = 0;
            int tileRadius = 0;
            int layerCount = 0;

            if (null != stream)
            {
                XDocument document = XDocument.Load(stream);

                foreach (XElement element in document.Descendants(SLGameEngine.Consts.MapAttributeMapName))
                    mapName = element.Value;
                foreach (XElement element in document.Descendants(SLGameEngine.Consts.MapAttributeWidthInTiles))
                    widthInTiles = Convert.ToInt32(element.Value);
                foreach (XElement element in document.Descendants(SLGameEngine.Consts.MapAttributeHeightInTiles))
                    heightInTiles = Convert.ToInt32(element.Value);
                foreach (XElement element in document.Descendants(SLGameEngine.Consts.MapAttributeTileRadius))
                    tileRadius = Convert.ToInt32(element.Value);
                foreach (XElement element in document.Descendants(SLGameEngine.Consts.MapAttributeLayerCount))
                    layerCount = Convert.ToInt32(element.Value);

                _terrainMgr.CreateNewMap(widthInTiles, heightInTiles, tileRadius, mapName, layerCount);

                for (int layer = 0; layer < layerCount; layer++)
                {
                    string layerAttribute = SLGameEngine.Consts.MapAttributeTileIDsLayer + layer.ToString();
                    string tileIDs = String.Empty;

                    foreach (XElement element in document.Descendants(layerAttribute))
                        tileIDs = element.Value;

                    string[] ids = tileIDs.Split(',');

                    for (int y = 0; y < _terrainMgr.TerrainHeightInTiles; y++)
                    {
                        for (int x = 0; x < _terrainMgr.TerrainWidthInTiles; x++)
                        {
                            int baseObjID = 0;
                            int index = (y * _terrainMgr.TerrainWidthInTiles) + x;
                            if (index < ids.Length)
                                baseObjID = Convert.ToInt32(ids[index]);
                            if (baseObjID != -1)
                            {
                                ObjectTerrain terrainObj = (ObjectTerrain)ObjectMgr.GetBaseObject(baseObjID);
                                if (null != terrainObj)
                                {
                                    Image img = terrainObj.GetLayerImage(layer);
                                    _terrainMgr.TerrainTiles[y, x].SetLayer(layer, baseObjID, Utils.GetImageFileName(img));
                                }
                            }
                        }
                    }
                }

                foreach (XElement element in document.Descendants(SLGameEngine.Consts.MapAttributeMapObject))
                {
                    int baseObjID = (int)element.Attribute(SLGameEngine.Consts.MapAttributeBaseObjectID);
                    int posX = (int)element.Attribute(SLGameEngine.Consts.MapAttributePosX);
                    int posY = (int)element.Attribute(SLGameEngine.Consts.MapAttributePosY);

                    SLGameEngine.ObjectMap mapObject = (SLGameEngine.ObjectMap) ObjectMgr.GetBaseObject(baseObjID);
                    if(null != mapObject)
                        AddMapObject(mapObject, posX, posY);
                }

                foreach (XElement element in document.Descendants(SLGameEngine.Consts.MapAttributeGameObject))
                {
                    int baseObjID = (int)element.Attribute(SLGameEngine.Consts.MapAttributeBaseObjectID);
                    int posX = (int)element.Attribute(SLGameEngine.Consts.MapAttributePosX);
                    int posY = (int)element.Attribute(SLGameEngine.Consts.MapAttributePosY);

                    SLGameEngine.ObjectGame gameObj = (SLGameEngine.ObjectGame)ObjectMgr.GetBaseObject(baseObjID);
                    if(null != gameObj)
                        AddGameObject(gameObj, posX, posY);
                }

                foreach (XElement element in document.Descendants(SLGameEngine.Consts.MapAttributeCreatureObject))
                {
                    int baseObjID = (int)element.Attribute(SLGameEngine.Consts.MapAttributeBaseObjectID);
                    int posX = (int)element.Attribute(SLGameEngine.Consts.MapAttributePosX);
                    int posY = (int)element.Attribute(SLGameEngine.Consts.MapAttributePosY);

                    SLGameEngine.ObjectCreature creatureObj = (SLGameEngine.ObjectCreature)ObjectMgr.GetBaseObject(baseObjID);
                    if(null != creatureObj)
                        AddCreatureObject(creatureObj, posX, posY);
                }
                stream.Close();
            }
        }
        public ObjectBase SelectObject(Point pt, ref Point selectionPoint)
        {
            ObjectBase collidedObject = ObjectMgr.GetCreatureObjectsCollision(pt, ref selectionPoint);
            if (collidedObject != null)
            {
                return collidedObject;
            }

            collidedObject = ObjectMgr.GetGameObjectsCollision(pt, ref selectionPoint);
            if (collidedObject != null)
            {
                return collidedObject;
            }

            collidedObject = ObjectMgr.GetMapObjectsCollision(pt, ref selectionPoint);
            if (collidedObject != null)
            {
                return collidedObject;
            }

            int tileX = (int)(pt.X / _terrainMgr.TerrainTileRadius);
            int tileY = (int)(pt.Y / _terrainMgr.TerrainTileRadius);

            return _terrainMgr.TerrainTiles[tileY, tileX];
        }

        public void StopAllAnimation()
        {
            foreach (ObjectCreature creature in ObjectMgr.CreatureObjects)
            {
                creature.AnimateInEditor = false;
            }
        }
    }
}
